#!/usr/local/bin/python
# Rob Dennis - Lurchington Q. Delacroix
# Keasby - TV renamer
# Project Homepage: http://code.google.com/p/keasby/
#--- Python Modules
import os
import sys
import logging
import urllib2
import unittest
from urlparse import urlsplit
from os.path import join as pjoin
from os.path import exists as pexists
#--- Keasby Modules
from keasby_toolbox import rm_rf
from base_classes import KeasbyTestCase

_default_cache_path = pjoin("cache")

def force_init(path=_default_cache_path):
    """
    This was designed to work with unit tests
    """

    logging.debug("manually creating cache dictory %s",path)
    if not os.path.exists(path):
        os.makedirs(path)

def force_purge(path=_default_cache_path):
    """
    This was designed to work with unit tests
    """

    logging.debug("manually removing cache dictory %s",path)
    rm_rf(path)

class KeasbyCache():
    def __init__(self,cache_path = None):
        """
        Class for caching URL files to disk, and the
        information to plain text.
        """

        if cache_path is None:
            cache_path = _default_cache_path

        self.dir = cache_path

        if not os.path.exists(self.dir):
            logging.debug("creating cache dictory %s",self.dir)
            os.makedirs(self.dir)
            self.new = True
        else:
            logging.debug("cache directory %s already exists",self.dir)
            self.new = False

#---Cache Interface

    def url_cache(self, url, cache_dir=None,
                    filename=None, overwrite=False, write=True):
        """
        Caches the contents of the url to the given directory,
        returns the path the file was saved to. If cache_dir and
        filename are left as None, sensible defaults are prodivided
        """

        if cache_dir is None:
            cache_dir = self.dir

        if filename is None:
            filename = self._get_url_filename(url)

        cache_dir,filename = self._shape_cache_info(cache_dir,filename,url)

        logging.debug("attempting to pull the contents of url: %s",url)
        try:
            url_obj = urllib2.urlopen(url)

        except Exception,error_message:
            raise TvDbCacheError("%s : %s"%(url,error_message))

        try:
            cache_path = self._cache(url_obj,
                                    pjoin(cache_dir,filename),
                                    overwrite,
                                    write)

        except TvDbCacheError,error_message:
            raise TvDbCacheError("error getting contents for %s",url)

        return cache_path

    def purge(self):
        """
        Removes the contents of the cache directory, along with the folder
        itself
        """

        logging.debug("attempting to remove %s",self.dir)
        rm_rf(self.dir)

    def __contains__(self, path):
        """
        overloading the in operator for _is_cached()
        """

        return self._is_cached(path)

    def _is_cached(self, path):
        """
        Convenience function for testing for the existing of path,
        tries as an absolute path, or as a path, relative to cache
        directory
        """

        return any(map(os.path.exists,[pjoin(self.dir,path),path]))


#---Cache Backend

    def _cache(self, file_obj, cache_path, overwrite=False, write=True):
        """
        Caches the contents of a file object to cache_path, return
        the path of that file
        """

        cache_path = self._make_cache_path(cache_path)

        if overwrite or not cache_path in self:
            logging.debug("caching file: %s",cache_path)

            content = file_obj.read()

            file_obj.close()

            if not content:
                raise TvDbCacheError("file has no contents")

            if write:
                outfile = open(cache_path,mode="wb")
                outfile.write(content)
                outfile.close()

            else:
                logging.debug("not writing file, returning content")
                return content

            return cache_path

        else:
            logging.debug("returning the contents of existing file: %s",
                            cache_path)

            return cache_path

    def _make_cache_path(self, cache_path):
        """
        Make the subpaths necessary to cache a file
        """

        if not os.path.exists(os.path.dirname(cache_path)):
            try:
                os.makedirs(os.path.dirname(cache_path))
            except Exception,error_message:
                logging.debug("error creating directory %s: %s",
                                os.path.dirname(cache_path),error_message)

                logging.debug("defaulting to: ")
                cache_path = pjoin(self.dir,os.path.basename(cache_path))
                logging.debug("defaulting to: %s",cache_path)

        return cache_path

    def _get_cache_path(self, path):
        """
        Returns a path that includes an absolute path with the cache
        directory
        """

        if self.dir not in path:
            return pjoin(self.dir,path)
        else:
            return path

    def _shape_cache_info(self, cache_dir, filename, url):
        """
        Convenience function for shaping the cache and filename
        """

        if filename is None:
            filename = self._get_url_filename(url)
        cache_dir = self._get_cache_path(cache_dir)

        return cache_dir,filename

#---Utility

    def _get_url_filename(self, url):
        """
        Get the last page of the url, and return a filename.
        eg: http://www.python.org/lib/module-urlparse.html would
        return 'module-urlparse.html'
        """

        path = urlsplit(url)[2] #index 2 is always the path

        #stepping through the url path in reverse order
        for possible_name in path.split("/")[::-1]:
            if possible_name:
                return possible_name

        logging.debug("unable to assume a filename from %s",url)
        return urlsplit(url)[1]

    def _make_url(self, url_netloc, *url_path):
        """
        Appends all parts of url_path to the given url_netloc using "/" as
        a delimeter
        """

        url_sequence = url_netloc,
        url_sequence = url_sequence + url_path

        return "/".join(url_sequence)


class KeasbyCacheError(Exception):
    """
    Generic TvDbCache Exception
    """
    pass


#--- keasby_cache

class KeasbyCacheTestCase(KeasbyTestCase):
    """
    Tests the correct interaction of the KeasbyCache base class
    which is intended to be subclassed for the unique requirements
    of each module
    """

    def setUp(self):
        """
        Instantiates the cache class
        """

        self._initialize_cache_directory()

        self.c = KeasbyCache()

        self.test_filename = pjoin(self.c.dir, "test-file.txt")
        self.test_message = "hello world"

        self.url = r"http://code.google.com/p/keasby/"

        open(self.test_filename,"w").write(self.test_message)

    def _initialize_cache_directory(self):
        """
        Intended to be overridden. Delete's cache directory manually
        to ensure a clean environment
        """

        force_purge()

    def test_cache_newness(self):
        """
        Tests the boolean flag set when the cache folder is created on
        instantiation, instead of that folder existing
        """

        self.assertLog(pexists(self.c.dir), "the path doesn't exist")
        self.assertLog(self.c.new, "the cache isn't new")

    def test_cache_operators(self):
        """
        Tests KeasbyCache operators. Currently:
        in : __contains__
        """

        self.assertLog(self.test_filename in self.c,
                        "%s not cached"%self.test_filename)

    def test_purge(self):
        """
        Tests that the cache directory is correctly removed on purging
        """

        self.c.purge()
        self.assertLog(pexists(self.c.dir), "%s still exists"%self.c.dir,
                        assertion=self.assertFalse)

    def test_url_cache(self):
        """
        Tests the caching of a given url to disk
        """

        cached_path =  self.c.url_cache(self.url)

        self.assertLog(pexists(cached_path),
                        "unable to cache %s to %s"%(self.url, cached_path),
                        "cached %s to %s"%(self.url, cached_path))

    def test_url_manual_name_cache(self):
        """
        Tests the caching of a given url to disk to a specified filename
        """
        test_filename = "temp.txt"

        cached_path = self.c.url_cache(self.url, filename=test_filename)

        self.assertLog(pexists(cached_path),
                        "unable to cache %s to %s"%(self.url, cached_path))

        self.assertEquals(os.path.basename(cached_path), test_filename,
                        ("cache not saved to manually " +
                            "specified name %s: %s"%(test_filename, cached_path)
                        ))


class KeasbyCacheLoadingTestCase(KeasbyCacheTestCase):
    """
    Runs the same tests as the base, but doesn't purge the directory at
    the end of every test
    """

    def _initialize_cache_directory(self):
        """
        Intended to be overridden. Delete's cache directory manually
        to ensure a clean environment
        """

        force_init()

    def test_cache_newness(self):
        """
        Tests the boolean flag set when the cache folder is created on
        instantiation, instead of that folder existing
        """

        self.assertLog(pexists(self.c.dir), "the path doesn't exist")
        self.assertLog(self.c.new, "the cache is new",
                        assertion=self.assertFalse)



if __name__ == "__main__":
    logging.basicConfig(level=logging.DEBUG,
                        format="%(levelname)s: %(message)s")

    unittest_list = []
    unittest_list.append(KeasbyCacheLoadingTestCase)
    unittest_list.append(KeasbyCacheTestCase)

    for unittest_class in unittest_list:
        suite = unittest.TestLoader().loadTestsFromTestCase(unittest_class)
        unittest.TextTestRunner(verbosity=2).run(suite)